/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/21 10:50
  *
  * @file       pd_usart.c
  * @brief      Xxx hardware driver.
  *
  * @note       This file contains the hardware driver for the Xxx.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/21     Chris        the first version
  *
  ******************************************************************************
  */


#include <stm32f4xx_ll_bus.h>
#include <stm32f4xx_ll_usart.h>
#include <stm32f4xx_ll_gpio.h>
#include <stdio.h>
#include <stdarg.h>
#include "pd_usart.h"

static void USART_init_(const USART *this, IRQn_Type irQnType, uint32_t PreemptPriority, uint32_t SubPriority) {
    // Set USART clock
    if      (this->USARTx == USART1) LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1);
    else if (this->USARTx == USART2) LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2);
    else if (this->USARTx == USART3) LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART3);
    else if (this->USARTx == UART4)  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_UART4);
    else if (this->USARTx == UART5)  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_UART5);
    else if (this->USARTx == USART6) LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART6);

    // GPIO init
    for (int i = 0; i < this->gpioList->gpioSize; ++i) {
        this->gpioList->gpios[i].Mode = LL_GPIO_MODE_ALTERNATE;
        this->gpioList->gpios[i].Speed = LL_GPIO_SPEED_FREQ_HIGH;
        if (this->USARTx == USART1 || this->USARTx == USART2 || this->USARTx == USART3) {
            this->gpioList->gpios[i].Alternate = LL_GPIO_AF_7;
        } else {
            this->gpioList->gpios[i].Alternate = LL_GPIO_AF_8;
        }
        GPIO_init(&this->gpioList->gpios[i]);
    }

    // USART init
    LL_USART_InitTypeDef USART_InitStruct = {0};
    LL_USART_StructInit(&USART_InitStruct);
    USART_InitStruct.BaudRate            = this->BaudRate;
    USART_InitStruct.DataWidth           = this->DataWidth;
    USART_InitStruct.StopBits            = this->StopBits;
    USART_InitStruct.Parity              = this->Parity;
    USART_InitStruct.TransferDirection   = this->TransferDirection;
    USART_InitStruct.HardwareFlowControl = this->HardwareFlowControl;
    USART_InitStruct.OverSampling        = this->OverSampling;
    LL_USART_Init(this->USARTx, &USART_InitStruct);

    LL_USART_ConfigAsyncMode(this->USARTx);

    // Set NVIC
    NVIC_SetPriority(irQnType, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),
                                                   PreemptPriority, SubPriority));
    NVIC_EnableIRQ(irQnType);

    LL_USART_EnableIT_RXNE(this->USARTx);
    LL_USART_Enable(this->USARTx);
}

static void USART_sendByte_(const USART *this, uint8_t byte) {
    LL_USART_TransmitData8(this->USARTx, byte);
    while (LL_USART_IsActiveFlag_TC(this->USARTx) == RESET);
}

static void USART_sendArray_(const USART *this, const uint8_t *array, uint16_t length) {
    for (uint16_t i = 0; i < length; i++) {
        this->vptr->send_byte(this, array[i]);
    }
}

static void USART_sendString_(const USART *this, char *String) {
    for (uint8_t i = 0; String[i] != '\0'; i++) {
        this->vptr->send_byte(this, String[i]);
    }
}

static uint32_t USART_pow(uint32_t y) {
    uint32_t result = 1;
    while (y--) {
        result *= 10;
    }
    return result;
}

static void USART_sendNum_(const USART *this, uint32_t number, uint8_t length) {
    for (uint8_t i = 0; i < length; i++) {
        this->vptr->send_byte(this, number / USART_pow(length - i - 1) % 10 + '0');
    }
}

static void USART_printf_(const USART *this, char *format, ...) {
    char String[100];
    va_list arg;
    va_start(arg, format);
    vsprintf(String, format, arg);
    va_end(arg);
    this->vptr->send_string(this, String);
}

static uint8_t USART_getRxFlag_(USART *this) {
    if (this->rxFlag == 1) {
        this->rxFlag = 0;
        return 1;
    }
    return 0;
}

void USART_constructor(USART *this, USART_TypeDef *USARTx, GPIOList *gpioList, uint32_t BaudRate) {
    static struct USARTVtbl vtbl = {
        .init        = &USART_init_,
        .send_byte   = &USART_sendByte_,
        .send_array  = &USART_sendArray_,
        .send_string = &USART_sendString_,
        .send_num    = &USART_sendNum_,
        .printf      = &USART_printf_,
        .get_rx_flag = &USART_getRxFlag_,
    };

    this->vptr = &vtbl;

    this->USARTx              = USARTx;
    this->gpioList            = gpioList;
    this->BaudRate            = BaudRate;
    this->DataWidth           = LL_USART_DATAWIDTH_8B;
    this->StopBits            = LL_USART_STOPBITS_1;
    this->Parity              = LL_USART_PARITY_NONE;
    this->TransferDirection   = LL_USART_DIRECTION_TX_RX;
    this->HardwareFlowControl = LL_USART_HWCONTROL_NONE;
    this->OverSampling        = LL_USART_OVERSAMPLING_16;
}
